package cn.geminis.lowcode.compiler.util;

import cn.geminis.lowcode.compiler.Context;
import cn.geminis.lowcode.compiler.code.AbstractCode;
import cn.geminis.lowcode.utils.Flags;
import cn.geminis.lowcode.utils.Utils;
import com.sun.source.tree.*;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * @author puddi
 */
public class JdkTreeMakerUtils {

    private JdkTreeMakerUtils() {
    }

    public static PackageTree packageDef(String name) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "PackageDecl",
                new Class[]{
                        JdkCompilerClass.LIST,
                        JdkCompilerClass.JC_EXPRESSION
                },
                Utils.nil(),
                ident(name)
        );
    }

    public static ClassTree classDef(ModifiersTree modifiers, String name, String... implementing) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "ClassDef",
                new Class[]{
                        JdkCompilerClass.JC_MODIFIERS,
                        JdkCompilerClass.NAME,
                        JdkCompilerClass.LIST,
                        JdkCompilerClass.JC_EXPRESSION,
                        JdkCompilerClass.LIST,
                        JdkCompilerClass.LIST,
                        JdkCompilerClass.LIST
                },
                modifiers,
                Context.INSTANCE.getElementUtils().getName(name),
                Utils.nil(),
                null,
                Utils.listFrom(Arrays.stream(implementing).map(JdkTreeMakerUtils::select).toList()),
                Utils.nil(),
                Utils.nil()
        );
    }

    public static MethodTree methodDef(
            ModifiersTree modifiers,
            String name,
            String returnType,
            List<String> returnTypeParameters,
            List<VariableTree> params,
            List<String> throwTypes,
            BlockTree body
    ) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "MethodDef",
                new Class[]{
                        JdkCompilerClass.JC_MODIFIERS,
                        JdkCompilerClass.NAME,
                        JdkCompilerClass.JC_EXPRESSION,
                        JdkCompilerClass.LIST,
                        JdkCompilerClass.JC_VARIABLE_DECL,
                        JdkCompilerClass.LIST,
                        JdkCompilerClass.LIST,
                        JdkCompilerClass.JC_BLOCK,
                        JdkCompilerClass.JC_EXPRESSION
                },
                modifiers,
                Objects.isNull(name) ? null : Context.INSTANCE.getElementUtils().getName(name),
                Objects.isNull(returnType) ? null : select(returnType),
                Utils.listFrom(
                        returnTypeParameters.stream().map(param ->
                                Utils.callConstructor(
                                        JdkCompilerClass.JC_TYPE_PARAMETER,
                                        new Class[]{
                                                JdkCompilerClass.NAME,
                                                JdkCompilerClass.LIST,
                                                JdkCompilerClass.LIST
                                        },
                                        Context.INSTANCE.getElementUtils().getName(param),
                                        Utils.nil(),
                                        Utils.nil()
                                )).toList()
                ),
                null,
                Utils.listFrom(params),
                Utils.nil(),
                body,
                null
        );
    }

    public static VariableTree variableDef(ModifiersTree modifiers, String varType, String name, ExpressionTree init, boolean declaredUsingVar) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "VarDef",
                new Class[]{
                        JdkCompilerClass.JC_MODIFIERS,
                        JdkCompilerClass.NAME,
                        JdkCompilerClass.JC_EXPRESSION,
                        JdkCompilerClass.JC_EXPRESSION,
                        boolean.class
                },
                modifiers,
                Context.INSTANCE.getElementUtils().getName(name),
                select(varType),
                init,
                declaredUsingVar
        );
    }

    public static LambdaExpressionTree lambda(List<VariableTree> params, Tree body) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Lambda",
                new Class[]{
                        JdkCompilerClass.LIST,
                        JdkCompilerClass.JC_TREE
                },
                Utils.listFrom(params),
                body
        );
    }

    public static BlockTree block(List<Tree> statements) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Block",
                new Class[]{
                        long.class,
                        JdkCompilerClass.LIST
                },
                Flags.BLOCK,
                Utils.listFrom(statements)
        );
    }

    public static ExpressionStatementTree exec(ExpressionTree expression) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Exec",
                new Class[]{
                        JdkCompilerClass.JC_EXPRESSION,
                },
                expression
        );
    }

    public static ReturnTree rtn(ExpressionTree expression) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Return",
                new Class[]{
                        JdkCompilerClass.JC_EXPRESSION,
                },
                expression
        );
    }

    public static IfTree ifElse(ExpressionTree condition, StatementTree thenPart) {
        return ifElse(condition, thenPart, null);
    }

    public static IfTree ifElse(ExpressionTree condition, StatementTree thenPart, StatementTree elsePart) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "If",
                new Class[]{
                        JdkCompilerClass.JC_EXPRESSION,
                        JdkCompilerClass.JC_STATEMENT,
                        JdkCompilerClass.JC_STATEMENT
                },
                condition,
                thenPart,
                elsePart
        );
    }

    public static DoWhileLoopTree doWhileLoop(StatementTree body, ExpressionTree condition) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "DoLoop",
                new Class[]{
                        JdkCompilerClass.JC_STATEMENT,
                        JdkCompilerClass.JC_EXPRESSION
                },
                body,
                condition
        );
    }

    public static MethodInvocationTree apply(ExpressionTree expression, ExpressionTree... args) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Apply",
                new Class[]{
                        JdkCompilerClass.LIST,
                        JdkCompilerClass.JC_EXPRESSION,
                        JdkCompilerClass.LIST
                },
                null,
                expression,
                Utils.listFrom(Arrays.stream(args).toList())
        );
    }

    public static AssignmentTree assign(Tree lhs, ExpressionTree rhs) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Assign",
                new Class[]{
                        JdkCompilerClass.JC_EXPRESSION,
                        JdkCompilerClass.JC_EXPRESSION
                },
                lhs,
                rhs
        );
    }

    public static Tree select(String expression) {
        var paramIndex = expression.indexOf("<");
        if (paramIndex > 0) {
            var paramIndexEnd = expression.indexOf(">", paramIndex);
            var returnParams = Arrays.stream(expression.substring(paramIndex + 1, paramIndexEnd).split(",")).map(String::trim).toList();
            var mainType = expression.substring(0, paramIndex);
            return typeApply(mainType, returnParams);
        } else if (expression.endsWith("[]")) {
            return arrayTypeIdent(expression);
        } else if ("?".equals(expression)) {
            var boundType = typeBoundKind(expression);
            return wildcard(boundType, null);
        } else {
            //系统默认类型定义为全大写
            if (expression.contains(".") || !expression.toUpperCase().equals(expression)) {
                var expressions = expression.split("\\.");
                var expr = ident(expressions[0]);
                for (int i = 1; i < expressions.length; i++) {
                    expr = select(expr, expressions[i]);
                }
                return expr;
            } else {
                return typeIdent(expression);
            }
        }
    }

    public static ExpressionTree select(ExpressionTree expression, String name) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Select",
                new Class[]{
                        JdkCompilerClass.JC_EXPRESSION,
                        JdkCompilerClass.NAME
                },
                expression,
                Context.INSTANCE.getElementUtils().getName(name)
        );
    }

    private static ExpressionTree ident(String name) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Ident",
                new Class[]{
                        JdkCompilerClass.NAME
                },
                Context.INSTANCE.getElementUtils().getName(name)
        );
    }

    private static PrimitiveTypeTree typeIdent(String name) {
        Enum<?>[] values = Utils.callMethod(
                JdkCompilerClass.TYPE_TAG,
                "values"
        );
        for (var value : values) {
            if (name.equals(value.name())) {
                return Utils.callMethod(
                        Context.INSTANCE.getTreeMaker(),
                        "TypeIdent",
                        value
                );
            }
        }
        throw new IllegalArgumentException("不支持的TypeTag：" + name);
    }

    private static Tree typeBoundKind(String name) {
        var enumConstants = JdkCompilerClass.BOUND_KIND.getEnumConstants();
        var select = Arrays.stream(enumConstants)
                .filter(item -> item.toString().equalsIgnoreCase(name))
                .findFirst()
                .orElseThrow();
        return Utils.callMethod(
                Context.INSTANCE.getTreeMaker(),
                "TypeBoundKind",
                select
        );
    }

    private static WildcardTree wildcard(Tree boundKind, Tree type) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Wildcard",
                new Class[]{
                        JdkCompilerClass.TYPE_BOUND_KIND,
                        JdkCompilerClass.JC_TREE
                },
                boundKind,
                type
        );
    }

    private static ArrayTypeTree arrayTypeIdent(String name) {
        var type = name.substring(0, name.length() - 2);
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "TypeArray",
                new Class[]{
                        JdkCompilerClass.JC_EXPRESSION
                },
                select(type)
        );
    }

    private static ParameterizedTypeTree typeApply(String name, List<String> parameters) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "TypeApply",
                new Class[]{
                        JdkCompilerClass.JC_EXPRESSION,
                        JdkCompilerClass.LIST
                },
                select(name),
                Utils.listFrom(parameters.stream().map(JdkTreeMakerUtils::select).toList())
        );
    }

    public static TypeCastTree typeCast(String type, ExpressionTree expression) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "TypeCast",
                new Class[]{
                        JdkCompilerClass.JC_TREE,
                        JdkCompilerClass.JC_EXPRESSION
                },
                select(type),
                expression
        );
    }

    public static UnaryTree unary(String type, ExpressionTree expression) {
        var tag = Utils.callMethodWithTypes(
                JdkCompilerClass.JC_TAG,
                "valueOf",
                new Class[]{
                        String.class
                },
                type
        );

        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Unary",
                new Class[]{
                        JdkCompilerClass.JC_TAG,
                        JdkCompilerClass.JC_EXPRESSION
                },
                tag,
                expression
        );
    }

    public static ModifiersTree modifiers(long flags, AnnotationTree... annotations) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Modifiers",
                new Class[]{
                        long.class,
                        JdkCompilerClass.LIST
                },
                flags,
                Utils.listFrom(Arrays.stream(annotations).toList())
        );
    }

    public static AnnotationTree annotation(String type, List<ExpressionTree> args) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Annotation",
                new Class[]{
                        JdkCompilerClass.JC_TREE,
                        JdkCompilerClass.LIST
                },
                select(type),
                Utils.listFrom(args)
        );
    }

    public static LiteralTree literal(Object value) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "Literal",
                new Class[]{
                        Object.class
                },
                value
        );
    }

    public static NewClassTree newClass(String clazz, List<String> parameters) {
        return Utils.callMethodWithTypes(
                Context.INSTANCE.getTreeMaker(),
                "NewClass",
                new Class[]{
                        JdkCompilerClass.JC_EXPRESSION,
                        JdkCompilerClass.LIST,
                        JdkCompilerClass.JC_EXPRESSION,
                        JdkCompilerClass.LIST,
                        JdkCompilerClass.JC_CLASS_DECL
                },
                null,
                Utils.nil(),
                select(clazz),
                Utils.listFrom(parameters.stream().map(JdkTreeMakerUtils::select).toList()),
                null
        );
    }

    public static void setPos(AbstractCode<?, ?> parentElement) {
        var newPos = Utils.getField(
                parentElement.getJdkTree(),
                "pos"
        );
        Utils.setField(
                Context.INSTANCE.getTreeMaker(),
                "pos",
                newPos
        );
    }

}
